home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / hplip / base / exif.py < prev    next >
Text File  |  2009-10-09  |  36KB  |  1,079 lines

  1. # Library to extract EXIF information in digital camera image files
  2. #
  3. # Contains code from "exifdump.py" originally written by Thierry Bousch
  4. # <bousch@topo.math.u-psud.fr> and released into the public domain.
  5. #
  6. # Updated and turned into general-purpose library by Gene Cash
  7. # <email gcash at cfl.rr.com>
  8. #
  9. # This copyright license is intended to be similar to the FreeBSD license. 
  10. #
  11. # Copyright 2002 Gene Cash All rights reserved. 
  12. #
  13. # Redistribution and use in source and binary forms, with or without
  14. # modification, are permitted provided that the following conditions are
  15. # met:
  16. #
  17. #    1. Redistributions of source code must retain the above copyright
  18. #       notice, this list of conditions and the following disclaimer.
  19. #    2. Redistributions in binary form must reproduce the above copyright
  20. #       notice, this list of conditions and the following disclaimer in the
  21. #       documentation and/or other materials provided with the
  22. #       distribution.
  23. #
  24. # THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
  25. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  26. # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  27. # DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
  28. # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  29. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  30. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  31. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  32. # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  33. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. # POSSIBILITY OF SUCH DAMAGE.
  35. #
  36. # This means you may do anything you want with this code, except claim you
  37. # wrote it. Also, if it breaks you get to keep both pieces.
  38. #
  39. # 21-AUG-99 TB  Last update by Thierry Bousch to his code.
  40. # 17-JAN-02 CEC Discovered code on web.
  41. #               Commented everything.
  42. #               Made small code improvements.
  43. #               Reformatted for readability.
  44. # 19-JAN-02 CEC Added ability to read TIFFs and JFIF-format JPEGs.
  45. #               Added ability to extract JPEG formatted thumbnail.
  46. #               Added ability to read GPS IFD (not tested).
  47. #               Converted IFD data structure to dictionaries indexed by
  48. #               tag name.
  49. #               Factored into library returning dictionary of IFDs plus
  50. #               thumbnail, if any.
  51. # 20-JAN-02 CEC Added MakerNote processing logic.
  52. #               Added Olympus MakerNote.
  53. #               Converted data structure to single-level dictionary, avoiding
  54. #               tag name collisions by prefixing with IFD name.  This makes
  55. #               it much easier to use.
  56. # 23-JAN-02 CEC Trimmed nulls from end of string values.
  57. # 25-JAN-02 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
  58. # 26-JAN-02 CEC Added ability to extract TIFF thumbnails.
  59. #               Added Nikon, Fujifilm, Casio MakerNotes.
  60. # 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
  61. #               IFD_Tag() object.
  62. # 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
  63. #
  64. # To do:
  65. # * Better printing of ratios
  66.  
  67. # field type descriptions as (length, abbreviation, full name) tuples
  68. FIELD_TYPES=(
  69.     (0, 'X',  'Proprietary'), # no such type
  70.     (1, 'B',  'Byte'),
  71.     (1, 'A',  'ASCII'),
  72.     (2, 'S',  'Short'),
  73.     (4, 'L',  'Long'),
  74.     (8, 'R',  'Ratio'),
  75.     (1, 'SB', 'Signed Byte'),
  76.     (1, 'U',  'Undefined'),
  77.     (2, 'SS', 'Signed Short'),
  78.     (4, 'SL', 'Signed Long'),
  79.     (8, 'SR', 'Signed Ratio')
  80.     )
  81.  
  82. # dictionary of main EXIF tag names
  83. # first element of tuple is tag name, optional second element is
  84. # another dictionary giving names to values
  85. EXIF_TAGS={
  86.     0x0100: ('ImageWidth', ),
  87.     0x0101: ('ImageLength', ),
  88.     0x0102: ('BitsPerSample', ),
  89.     0x0103: ('Compression',
  90.              {1: 'Uncompressed TIFF',
  91.               6: 'JPEG Compressed'}),
  92.     0x0106: ('PhotometricInterpretation', ),
  93.     0x010A: ('FillOrder', ),
  94.     0x010D: ('DocumentName', ),
  95.     0x010E: ('ImageDescription', ),
  96.     0x010F: ('Make', ),
  97.     0x0110: ('Model', ),
  98.     0x0111: ('StripOffsets', ),
  99.     0x0112: ('Orientation', ),
  100.     0x0115: ('SamplesPerPixel', ),
  101.     0x0116: ('RowsPerStrip', ),
  102.     0x0117: ('StripByteCounts', ),
  103.     0x011A: ('XResolution', ),
  104.     0x011B: ('YResolution', ),
  105.     0x011C: ('PlanarConfiguration', ),
  106.     0x0128: ('ResolutionUnit',
  107.              {1: 'Not Absolute',
  108.               2: 'Pixels/Inch',
  109.               3: 'Pixels/Centimeter'}),
  110.     0x012D: ('TransferFunction', ),
  111.     0x0131: ('Software', ),
  112.     0x0132: ('DateTime', ),
  113.     0x013B: ('Artist', ),
  114.     0x013E: ('WhitePoint', ),
  115.     0x013F: ('PrimaryChromaticities', ),
  116.     0x0156: ('TransferRange', ),
  117.     0x0200: ('JPEGProc', ),
  118.     0x0201: ('JPEGInterchangeFormat', ),
  119.     0x0202: ('JPEGInterchangeFormatLength', ),
  120.     0x0211: ('YCbCrCoefficients', ),
  121.     0x0212: ('YCbCrSubSampling', ),
  122.     0x0213: ('YCbCrPositioning', ),
  123.     0x0214: ('ReferenceBlackWhite', ),
  124.     0x828D: ('CFARepeatPatternDim', ),
  125.     0x828E: ('CFAPattern', ),
  126.     0x828F: ('BatteryLevel', ),
  127.     0x8298: ('Copyright', ),
  128.     0x829A: ('ExposureTime', ),
  129.     0x829D: ('FNumber', ),
  130.     0x83BB: ('IPTC/NAA', ),
  131.     0x8769: ('ExifOffset', ),
  132.     0x8773: ('InterColorProfile', ),
  133.     0x8822: ('ExposureProgram',
  134.              {0: 'Unidentified',
  135.               1: 'Manual',
  136.               2: 'Program Normal',
  137.               3: 'Aperture Priority',
  138.               4: 'Shutter Priority',
  139.               5: 'Program Creative',
  140.               6: 'Program Action',
  141.               7: 'Portrait Mode',
  142.               8: 'Landscape Mode'}),
  143.     0x8824: ('SpectralSensitivity', ),
  144.     0x8825: ('GPSInfo', ),
  145.     0x8827: ('ISOSpeedRatings', ),
  146.     0x8828: ('OECF', ),
  147.     # print as string
  148.     0x9000: ('ExifVersion', lambda x: ''.join(map(chr, x))),
  149.     0x9003: ('DateTimeOriginal', ),
  150.     0x9004: ('DateTimeDigitized', ),
  151.     0x9101: ('ComponentsConfiguration',
  152.              {0: '',
  153.               1: 'Y',
  154.               2: 'Cb',
  155.               3: 'Cr',
  156.               4: 'Red',
  157.               5: 'Green',
  158.               6: 'Blue'}),
  159.     0x9102: ('CompressedBitsPerPixel', ),
  160.     0x9201: ('ShutterSpeedValue', ),
  161.     0x9202: ('ApertureValue', ),
  162.     0x9203: ('BrightnessValue', ),
  163.     0x9204: ('ExposureBiasValue', ),
  164.     0x9205: ('MaxApertureValue', ),
  165.     0x9206: ('SubjectDistance', ),
  166.     0x9207: ('MeteringMode',
  167.              {0: 'Unidentified',
  168.               1: 'Average',
  169.               2: 'CenterWeightedAverage',
  170.               3: 'Spot',
  171.               4: 'MultiSpot'}),
  172.     0x9208: ('LightSource',
  173.              {0:   'Unknown',
  174.               1:   'Daylight',
  175.               2:   'Fluorescent',
  176.               3:   'Tungsten',
  177.               10:  'Flash',
  178.               17:  'Standard Light A',
  179.               18:  'Standard Light B',
  180.               19:  'Standard Light C',
  181.               20:  'D55',
  182.               21:  'D65',
  183.               22:  'D75',
  184.               255: 'Other'}),
  185.     0x9209: ('Flash', {0:  'No',
  186.                        1:  'Fired',
  187.                        5:  'Fired (?)', # no return sensed
  188.                        7:  'Fired (!)', # return sensed
  189.                        9:  'Fill Fired',
  190.                        13: 'Fill Fired (?)',
  191.                        15: 'Fill Fired (!)',
  192.                        16: 'Off',
  193.                        24: 'Auto Off',
  194.                        25: 'Auto Fired',
  195.                        29: 'Auto Fired (?)',
  196.                        31: 'Auto Fired (!)',
  197.                        32: 'Not Available'}),
  198.     0x920A: ('FocalLength', ),
  199.     0x927C: ('MakerNote', ),
  200.     # print as string
  201.     0x9286: ('UserComment', lambda x: ''.join(map(chr, x))),
  202.     0x9290: ('SubSecTime', ),
  203.     0x9291: ('SubSecTimeOriginal', ),
  204.     0x9292: ('SubSecTimeDigitized', ),
  205.     # print as string
  206.     0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))),
  207.     0xA001: ('ColorSpace', ),
  208.     0xA002: ('ExifImageWidth', ),
  209.     0xA003: ('ExifImageLength', ),
  210.     0xA005: ('InteroperabilityOffset', ),
  211.     0xA20B: ('FlashEnergy', ),               # 0x920B in TIFF/EP
  212.     0xA20C: ('SpatialFrequencyResponse', ),  # 0x920C    -  -
  213.     0xA20E: ('FocalPlaneXResolution', ),     # 0x920E    -  -
  214.     0xA20F: ('FocalPlaneYResolution', ),     # 0x920F    -  -
  215.     0xA210: ('FocalPlaneResolutionUnit', ),  # 0x9210    -  -
  216.     0xA214: ('SubjectLocation', ),           # 0x9214    -  -
  217.     0xA215: ('ExposureIndex', ),             # 0x9215    -  -
  218.     0xA217: ('SensingMethod', ),             # 0x9217    -  -
  219.     0xA300: ('FileSource',
  220.              {3: 'Digital Camera'}),
  221.     0xA301: ('SceneType',
  222.              {1: 'Directly Photographed'}),
  223.     }
  224.  
  225. # interoperability tags
  226. INTR_TAGS={
  227.     0x0001: ('InteroperabilityIndex', ),
  228.     0x0002: ('InteroperabilityVersion', ),
  229.     0x1000: ('RelatedImageFileFormat', ),
  230.     0x1001: ('RelatedImageWidth', ),
  231.     0x1002: ('RelatedImageLength', ),
  232.     }
  233.  
  234. # GPS tags (not used yet, haven't seen camera with GPS)
  235. GPS_TAGS={
  236.     0x0000: ('GPSVersionID', ),
  237.     0x0001: ('GPSLatitudeRef', ),
  238.     0x0002: ('GPSLatitude', ),
  239.     0x0003: ('GPSLongitudeRef', ),
  240.     0x0004: ('GPSLongitude', ),
  241.     0x0005: ('GPSAltitudeRef', ),
  242.     0x0006: ('GPSAltitude', ),
  243.     0x0007: ('GPSTimeStamp', ),
  244.     0x0008: ('GPSSatellites', ),
  245.     0x0009: ('GPSStatus', ),
  246.     0x000A: ('GPSMeasureMode', ),
  247.     0x000B: ('GPSDOP', ),
  248.     0x000C: ('GPSSpeedRef', ),
  249.     0x000D: ('GPSSpeed', ),
  250.     0x000E: ('GPSTrackRef', ),
  251.     0x000F: ('GPSTrack', ),
  252.     0x0010: ('GPSImgDirectionRef', ),
  253.     0x0011: ('GPSImgDirection', ),
  254.     0x0012: ('GPSMapDatum', ),
  255.     0x0013: ('GPSDestLatitudeRef', ),
  256.     0x0014: ('GPSDestLatitude', ),
  257.     0x0015: ('GPSDestLongitudeRef', ),
  258.     0x0016: ('GPSDestLongitude', ),
  259.     0x0017: ('GPSDestBearingRef', ),
  260.     0x0018: ('GPSDestBearing', ),
  261.     0x0019: ('GPSDestDistanceRef', ),
  262.     0x001A: ('GPSDestDistance', )
  263.     }
  264.  
  265. # Nikon E99x MakerNote Tags
  266. # http://members.tripod.com/~tawba/990exif.htm
  267. MAKERNOTE_NIKON_NEWER_TAGS={
  268.     0x0002: ('ISOSetting', ),
  269.     0x0003: ('ColorMode', ),
  270.     0x0004: ('Quality', ),
  271.     0x0005: ('Whitebalance', ),
  272.     0x0006: ('ImageSharpening', ),
  273.     0x0007: ('FocusMode', ),
  274.     0x0008: ('FlashSetting', ),
  275.     0x000F: ('ISOSelection', ),
  276.     0x0080: ('ImageAdjustment', ),
  277.     0x0082: ('AuxiliaryLens', ),
  278.     0x0085: ('ManualFocusDistance', ),
  279.     0x0086: ('DigitalZoomFactor', ),
  280.     0x0088: ('AFFocusPosition',
  281.              {0x0000: 'Center',
  282.               0x0100: 'Top',
  283.               0x0200: 'Bottom',
  284.               0x0300: 'Left',
  285.               0x0400: 'Right'}),
  286.     0x0094: ('Saturation',
  287.              {-3: 'B&W',
  288.               -2: '-2',
  289.               -1: '-1',
  290.               0:  '0',
  291.               1:  '1',
  292.               2:  '2'}),
  293.     0x0095: ('NoiseReduction', ),
  294.     0x0010: ('DataDump', )
  295.     }
  296.  
  297. MAKERNOTE_NIKON_OLDER_TAGS={
  298.     0x0003: ('Quality',
  299.              {1: 'VGA Basic',
  300.               2: 'VGA Normal',
  301.               3: 'VGA Fine',
  302.               4: 'SXGA Basic',
  303.               5: 'SXGA Normal',
  304.               6: 'SXGA Fine'}),
  305.     0x0004: ('ColorMode',
  306.              {1: 'Color',
  307.               2: 'Monochrome'}),
  308.     0x0005: ('ImageAdjustment',
  309.              {0: 'Normal',
  310.               1: 'Bright+',
  311.               2: 'Bright-',
  312.               3: 'Contrast+',
  313.               4: 'Contrast-'}),
  314.     0x0006: ('CCDSpeed',
  315.              {0: 'ISO 80',
  316.               2: 'ISO 160',
  317.               4: 'ISO 320',
  318.               5: 'ISO 100'}),
  319.     0x0007: ('WhiteBalance',
  320.              {0: 'Auto',
  321.               1: 'Preset',
  322.               2: 'Daylight',
  323.               3: 'Incandescent',
  324.               4: 'Fluorescent',
  325.               5: 'Cloudy',
  326.               6: 'Speed Light'})
  327.     }
  328.  
  329. # decode Olympus SpecialMode tag in MakerNote
  330. def olympus_special_mode(v):
  331.     a={
  332.         0: 'Normal',
  333.         1: 'Unknown',
  334.         2: 'Fast',
  335.         3: 'Panorama'}
  336.     b={
  337.         0: 'Non-panoramic',
  338.         1: 'Left to right',
  339.         2: 'Right to left',
  340.         3: 'Bottom to top',
  341.         4: 'Top to bottom'}
  342.     return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
  343.  
  344. MAKERNOTE_OLYMPUS_TAGS={
  345.     # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
  346.     # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
  347.     0x0100: ('JPEGThumbnail', ),
  348.     0x0200: ('SpecialMode', olympus_special_mode),
  349.     0x0201: ('JPEGQual',
  350.              {1: 'SQ',
  351.               2: 'HQ',
  352.               3: 'SHQ'}),
  353.     0x0202: ('Macro',
  354.              {0: 'Normal',
  355.               1: 'Macro'}),
  356.     0x0204: ('DigitalZoom', ),
  357.     0x0207: ('SoftwareRelease',  ),
  358.     0x0208: ('PictureInfo',  ),
  359.     # print as string
  360.     0x0209: ('CameraID', lambda x: ''.join(map(chr, x))), 
  361.     0x0F00: ('DataDump',  )
  362.     }
  363.  
  364. MAKERNOTE_CASIO_TAGS={
  365.     0x0001: ('RecordingMode',
  366.              {1: 'Single Shutter',
  367.               2: 'Panorama',
  368.               3: 'Night Scene',
  369.               4: 'Portrait',
  370.               5: 'Landscape'}),
  371.     0x0002: ('Quality',
  372.              {1: 'Economy',
  373.               2: 'Normal',
  374.               3: 'Fine'}),
  375.     0x0003: ('FocusingMode',
  376.              {2: 'Macro',
  377.               3: 'Auto Focus',
  378.               4: 'Manual Focus',
  379.               5: 'Infinity'}),
  380.     0x0004: ('FlashMode',
  381.              {1: 'Auto',
  382.               2: 'On',
  383.               3: 'Off',
  384.               4: 'Red Eye Reduction'}),
  385.     0x0005: ('FlashIntensity',
  386.              {11: 'Weak',
  387.               13: 'Normal',
  388.               15: 'Strong'}),
  389.     0x0006: ('Object Distance', ),
  390.     0x0007: ('WhiteBalance',
  391.              {1:   'Auto',
  392.               2:   'Tungsten',
  393.               3:   'Daylight',
  394.               4:   'Fluorescent',
  395.               5:   'Shade',
  396.               129: 'Manual'}),
  397.     0x000B: ('Sharpness',
  398.              {0: 'Normal',
  399.               1: 'Soft',
  400.               2: 'Hard'}),
  401.     0x000C: ('Contrast',
  402.              {0: 'Normal',
  403.               1: 'Low',
  404.               2: 'High'}),
  405.     0x000D: ('Saturation',
  406.              {0: 'Normal',
  407.               1: 'Low',
  408.               2: 'High'}),
  409.     0x0014: ('CCDSpeed',
  410.              {64:  'Normal',
  411.               80:  'Normal',
  412.               100: 'High',
  413.               125: '+1.0',
  414.               244: '+3.0',
  415.               250: '+2.0',})
  416.     }
  417.  
  418. MAKERNOTE_FUJIFILM_TAGS={
  419.     0x0000: ('NoteVersion', lambda x: ''.join(map(chr, x))),
  420.     0x1000: ('Quality', ),
  421.     0x1001: ('Sharpness',
  422.              {1: 'Soft',
  423.               2: 'Soft',
  424.               3: 'Normal',
  425.               4: 'Hard',
  426.               5: 'Hard'}),
  427.     0x1002: ('WhiteBalance',
  428.              {0:    'Auto',
  429.               256:  'Daylight',
  430.               512:  'Cloudy',
  431.               768:  'DaylightColor-Fluorescent',
  432.               769:  'DaywhiteColor-Fluorescent',
  433.               770:  'White-Fluorescent',
  434.               1024: 'Incandescent',
  435.               3840: 'Custom'}),
  436.     0x1003: ('Color',
  437.              {0:   'Normal',
  438.               256: 'High',
  439.               512: 'Low'}),
  440.     0x1004: ('Tone',
  441.              {0:   'Normal',
  442.               256: 'High',
  443.               512: 'Low'}),
  444.     0x1010: ('FlashMode',
  445.              {0: 'Auto',
  446.               1: 'On',
  447.               2: 'Off',
  448.               3: 'Red Eye Reduction'}),
  449.     0x1011: ('FlashStrength', ),
  450.     0x1020: ('Macro',
  451.              {0: 'Off',
  452.               1: 'On'}),
  453.     0x1021: ('FocusMode',
  454.              {0: 'Auto',
  455.               1: 'Manual'}),
  456.     0x1030: ('SlowSync',
  457.              {0: 'Off',
  458.               1: 'On'}),
  459.     0x1031: ('PictureMode',
  460.              {0:   'Auto',
  461.               1:   'Portrait',
  462.               2:   'Landscape',
  463.               4:   'Sports',
  464.               5:   'Night',
  465.               6:   'Program AE',
  466.               256: 'Aperture Priority AE',
  467.               512: 'Shutter Priority AE',
  468.               768: 'Manual Exposure'}),
  469.     0x1100: ('MotorOrBracket',
  470.              {0: 'Off',
  471.               1: 'On'}),
  472.     0x1300: ('BlurWarning',
  473.              {0: 'Off',
  474.               1: 'On'}),
  475.     0x1301: ('FocusWarning',
  476.              {0: 'Off',
  477.               1: 'On'}),
  478.     0x1302: ('AEWarning',
  479.              {0: 'Off',
  480.               1: 'On'})
  481.     }
  482.  
  483. MAKERNOTE_CANON_TAGS={
  484.     0x0006: ('ImageType', ),
  485.     0x0007: ('FirmwareVersion', ),
  486.     0x0008: ('ImageNumber', ),
  487.     0x0009: ('OwnerName', )
  488.     }
  489.  
  490. # see http://www.burren.cx/david/canon.html by David Burren
  491. # this is in element offset, name, optional value dictionary format
  492. MAKERNOTE_CANON_TAG_0x001={
  493.     1: ('Macromode',
  494.         {1: 'Macro',
  495.          2: 'Normal'}),
  496.     2: ('SelfTimer', ),
  497.     3: ('Quality',
  498.         {2: 'Normal',
  499.          3: 'Fine',
  500.          5: 'Superfine'}),
  501.     4: ('FlashMode',
  502.         {0: 'Flash Not Fired',
  503.          1: 'Auto',
  504.          2: 'On',
  505.          3: 'Red-Eye Reduction',
  506.          4: 'Slow Synchro',
  507.          5: 'Auto + Red-Eye Reduction',
  508.          6: 'On + Red-Eye Reduction',
  509.          16: 'external flash'}),
  510.     5: ('ContinuousDriveMode',
  511.         {0: 'Single Or Timer',
  512.          1: 'Continuous'}),
  513.     7: ('FocusMode',
  514.         {0: 'One-Shot',
  515.          1: 'AI Servo',
  516.          2: 'AI Focus',
  517.          3: 'MF',
  518.          4: 'Single',
  519.          5: 'Continuous',
  520.          6: 'MF'}),
  521.     10: ('ImageSize',
  522.          {0: 'Large',
  523.           1: 'Medium',
  524.           2: 'Small'}),
  525.     11: ('EasyShootingMode',
  526.          {0: 'Full Auto',
  527.           1: 'Manual',
  528.           2: 'Landscape',
  529.           3: 'Fast Shutter',
  530.           4: 'Slow Shutter',
  531.           5: 'Night',
  532.           6: 'B&W',
  533.           7: 'Sepia',
  534.           8: 'Portrait',
  535.           9: 'Sports',
  536.           10: 'Macro/Close-Up',
  537.           11: 'Pan Focus'}),
  538.     12: ('DigitalZoom',
  539.          {0: 'None',
  540.           1: '2x',
  541.           2: '4x'}),
  542.     13: ('Contrast',
  543.          {0xFFFF: 'Low',
  544.           0: 'Normal',
  545.           1: 'High'}),
  546.     14: ('Saturation',
  547.          {0xFFFF: 'Low',
  548.           0: 'Normal',
  549.           1: 'High'}),
  550.     15: ('Sharpness',
  551.          {0xFFFF: 'Low',
  552.           0: 'Normal',
  553.           1: 'High'}),
  554.     16: ('ISO',
  555.          {0: 'See ISOSpeedRatings Tag',
  556.           15: 'Auto',
  557.           16: '50',
  558.           17: '100',
  559.           18: '200',
  560.           19: '400'}),
  561.     17: ('MeteringMode',
  562.          {3: 'Evaluative',
  563.           4: 'Partial',
  564.           5: 'Center-weighted'}),
  565.     18: ('FocusType',
  566.          {0: 'Manual',
  567.           1: 'Auto',
  568.           3: 'Close-Up (Macro)',
  569.           8: 'Locked (Pan Mode)'}),
  570.     19: ('AFPointSelected',
  571.          {0x3000: 'None (MF)',
  572.           0x3001: 'Auto-Selected',
  573.           0x3002: 'Right',
  574.           0x3003: 'Center',
  575.           0x3004: 'Left'}),
  576.     20: ('ExposureMode',
  577.          {0: 'Easy Shooting',
  578.           1: 'Program',
  579.           2: 'Tv-priority',
  580.           3: 'Av-priority',
  581.           4: 'Manual',
  582.           5: 'A-DEP'}),
  583.     23: ('LongFocalLengthOfLensInFocalUnits', ),
  584.     24: ('ShortFocalLengthOfLensInFocalUnits', ),
  585.     25: ('FocalUnitsPerMM', ),
  586.     28: ('FlashActivity',
  587.          {0: 'Did Not Fire',
  588.           1: 'Fired'}),
  589.     29: ('FlashDetails',
  590.          {14: 'External E-TTL',
  591.           13: 'Internal Flash',
  592.           11: 'FP Sync Used',
  593.           7: '2nd("Rear")-Curtain Sync Used',
  594.           4: 'FP Sync Enabled'}),
  595.     32: ('FocusMode',
  596.          {0: 'Single',
  597.           1: 'Continuous'})
  598.     }
  599.  
  600. MAKERNOTE_CANON_TAG_0x004={
  601.     7: ('WhiteBalance',
  602.         {0: 'Auto',
  603.          1: 'Sunny',
  604.          2: 'Cloudy',
  605.          3: 'Tungsten',
  606.          4: 'Fluorescent',
  607.          5: 'Flash',
  608.          6: 'Custom'}),
  609.     9: ('SequenceNumber', ),
  610.     14: ('AFPointUsed', ),
  611.     15: ('FlashBias',
  612.         {0XFFC0: '-2 EV',
  613.          0XFFCC: '-1.67 EV',
  614.          0XFFD0: '-1.50 EV',
  615.          0XFFD4: '-1.33 EV',
  616.          0XFFE0: '-1 EV',
  617.          0XFFEC: '-0.67 EV',
  618.          0XFFF0: '-0.50 EV',
  619.          0XFFF4: '-0.33 EV',
  620.          0X0000: '0 EV',
  621.          0X000C: '0.33 EV',
  622.          0X0010: '0.50 EV',
  623.          0X0014: '0.67 EV',
  624.          0X0020: '1 EV',
  625.          0X002C: '1.33 EV',
  626.          0X0030: '1.50 EV',
  627.          0X0034: '1.67 EV',
  628.          0X0040: '2 EV'}), 
  629.     19: ('SubjectDistance', )
  630.     }
  631.  
  632. # extract multibyte integer in Motorola format (little endian)
  633. def s2n_motorola(str):
  634.     x=0
  635.     for c in str:
  636.         x=(x << 8) | ord(c)
  637.     return x
  638.  
  639. # extract multibyte integer in Intel format (big endian)
  640. def s2n_intel(str):
  641.     x=0
  642.     y=0L
  643.     for c in str:
  644.         x=x | (ord(c) << y)
  645.         y=y+8
  646.     return x
  647.  
  648. # ratio object that eventually will be able to reduce itself to lowest
  649. # common denominator for printing
  650. def gcd(a, b):
  651.    if b == 0:
  652.       return a
  653.    else:
  654.       return gcd(b, a % b)
  655.  
  656. class Ratio:
  657.     def __init__(self, num, den):
  658.         self.num=num
  659.         self.den=den
  660.  
  661.     def __repr__(self):
  662.         self.reduce()
  663.         if self.den == 1:
  664.             return str(self.num)
  665.         return '%d/%d' % (self.num, self.den)
  666.  
  667.     def reduce(self):
  668.         div=gcd(self.num, self.den)
  669.         if div > 1:
  670.             self.num=self.num/div
  671.             self.den=self.den/div
  672.  
  673. # for ease of dealing with tags
  674. class IFD_Tag:
  675.     def __init__(self, printable, tag, field_type, values, field_offset,
  676.                  field_length):
  677.         # printable version of data
  678.         self.printable=printable
  679.         # tag ID number
  680.         self.tag=tag
  681.         # field type as index into FIELD_TYPES
  682.         self.field_type=field_type
  683.         # offset of start of field in bytes from beginning of IFD
  684.         self.field_offset=field_offset
  685.         # length of data field in bytes
  686.         self.field_length=field_length
  687.         # either a string or array of data items
  688.         self.values=values
  689.  
  690.     def __str__(self):
  691.         return self.printable
  692.  
  693.     def __repr__(self):
  694.         return '(0x%04X) %s=%s @ %d' % (self.tag,
  695.                                         FIELD_TYPES[self.field_type][2],
  696.                                         self.printable,
  697.                                         self.field_offset)
  698.  
  699. # class that handles an EXIF header
  700. class EXIF_header:
  701.     def __init__(self, file, endian, offset, debug=0):
  702.         self.file=file
  703.         self.endian=endian
  704.         self.offset=offset
  705.         self.debug=debug
  706.         self.tags={}
  707.  
  708.     # convert slice to integer, based on sign and endian flags
  709.     def s2n(self, offset, length, signed=0):
  710.         self.file.seek(self.offset+offset)
  711.         slice=self.file.read(length)
  712.         if self.endian == 'I':
  713.             val=s2n_intel(slice)
  714.         else:
  715.             val=s2n_motorola(slice)
  716.         # Sign extension ?
  717.         if signed:
  718.             #msb=1 << (8*length-1)
  719.             #if val & msb:
  720.             #    val=val-(msb << 1)
  721.             pass
  722.         return val
  723.  
  724.     # convert offset to string
  725.     def n2s(self, offset, length):
  726.         s=''
  727.         for i in range(length):
  728.             if self.endian == 'I':
  729.                 s=s+chr(offset & 0xFF)
  730.             else:
  731.                 s=chr(offset & 0xFF)+s
  732.             offset=offset >> 8
  733.         return s
  734.  
  735.     # return first IFD
  736.     def first_IFD(self):
  737.         return self.s2n(4, 4)
  738.  
  739.     # return pointer to next IFD
  740.     def next_IFD(self, ifd):
  741.         entries=self.s2n(ifd, 2)
  742.         return self.s2n(ifd+2+12*entries, 4)
  743.  
  744.     # return list of IFDs in header
  745.     def list_IFDs(self):
  746.         i=self.first_IFD()
  747.         a=[]
  748.         while i:
  749.             a.append(i)
  750.             i=self.next_IFD(i)
  751.         return a
  752.  
  753.     # return list of entries in this IFD
  754.     def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS):
  755.         entries=self.s2n(ifd, 2)
  756.         for i in range(entries):
  757.             entry=ifd+2+12*i
  758.             tag=self.s2n(entry, 2)
  759.             field_type=self.s2n(entry+2, 2)
  760.             if not 0 < field_type < len(FIELD_TYPES):
  761.                 # unknown field type
  762.                 raise ValueError, \
  763.                       'unknown type %d in tag 0x%04X' % (field_type, tag)
  764.             typelen=FIELD_TYPES[field_type][0]
  765.             count=self.s2n(entry+4, 4)
  766.             offset=entry+8
  767.             if count*typelen > 4:
  768.                 # not the value, it's a pointer to the value
  769.                 offset=self.s2n(offset, 4)
  770.             field_offset=offset
  771.             if field_type == 2:
  772.                 # special case: null-terminated ASCII string
  773.                 if count != 0:
  774.                     self.file.seek(self.offset+offset)
  775.                     values=self.file.read(count).strip().replace('\x00','')
  776.                 else:
  777.                     values=''
  778.             else:
  779.                 values=[]
  780.                 signed=(field_type in [6, 8, 9, 10])
  781.                 for j in range(count):
  782.                     if field_type in (5, 10):
  783.                         # a ratio
  784.                         value_j=Ratio(self.s2n(offset,   4, signed),
  785.                                       self.s2n(offset+4, 4, signed))
  786.                     else:
  787.                         value_j=self.s2n(offset, typelen, signed)
  788.                     values.append(value_j)
  789.                     offset=offset+typelen
  790.             # now "values" is either a string or an array
  791.             if count == 1 and field_type != 2:
  792.                 printable=str(values[0])
  793.             else:
  794.                 printable=str(values)
  795.             # figure out tag name
  796.             tag_entry=dict.get(tag)
  797.             if tag_entry:
  798.                 tag_name=tag_entry[0]
  799.                 if len(tag_entry) != 1:
  800.                     # optional 2nd tag element is present
  801.                     if callable(tag_entry[1]):
  802.                         # call mapping function
  803.                         printable=tag_entry[1](values)
  804.                     else:
  805.                         printable=''
  806.                         for i in values:
  807.                             # use LUT for this tag
  808.                             printable+=tag_entry[1].get(i, repr(i))
  809.             else:
  810.                 tag_name='Tag 0x%04X' % tag
  811.             self.tags[ifd_name+' '+tag_name]=IFD_Tag(printable, tag,
  812.                                                      field_type,
  813.                                                      values, field_offset,
  814.                                                      count*typelen)
  815.             if self.debug:
  816.                 print '    %s: %s' % (tag_name,
  817.                                       repr(self.tags[ifd_name+' '+tag_name]))
  818.  
  819.     # extract uncompressed TIFF thumbnail (like pulling teeth)
  820.     # we take advantage of the pre-existing layout in the thumbnail IFD as
  821.     # much as possible
  822.     def extract_TIFF_thumbnail(self, thumb_ifd):
  823.         entries=self.s2n(thumb_ifd, 2)
  824.         # this is header plus offset to IFD ...
  825.         if self.endian == 'M':
  826.             tiff='MM\x00*\x00\x00\x00\x08'
  827.         else:
  828.             tiff='II*\x00\x08\x00\x00\x00'
  829.         # ... plus thumbnail IFD data plus a null "next IFD" pointer
  830.         self.file.seek(self.offset+thumb_ifd)
  831.         tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00'
  832.  
  833.         # fix up large value offset pointers into data area
  834.         for i in range(entries):
  835.             entry=thumb_ifd+2+12*i
  836.             tag=self.s2n(entry, 2)
  837.             field_type=self.s2n(entry+2, 2)
  838.             typelen=FIELD_TYPES[field_type][0]
  839.             count=self.s2n(entry+4, 4)
  840.             oldoff=self.s2n(entry+8, 4)
  841.             # start of the 4-byte pointer area in entry
  842.             ptr=i*12+18
  843.             # remember strip offsets location
  844.             if tag == 0x0111:
  845.                 strip_off=ptr
  846.                 strip_len=count*typelen
  847.             # is it in the data area?
  848.             if count*typelen > 4:
  849.                 # update offset pointer (nasty "strings are immutable" crap)
  850.                 # should be able to say "tiff[ptr:ptr+4]=newoff"
  851.                 newoff=len(tiff)
  852.                 tiff=tiff[:ptr]+self.n2s(newoff, 4)+tiff[ptr+4:]
  853.                 # remember strip offsets location
  854.                 if tag == 0x0111:
  855.                     strip_off=newoff
  856.                     strip_len=4
  857.                 # get original data and store it
  858.                 self.file.seek(self.offset+oldoff)
  859.                 tiff+=self.file.read(count*typelen)
  860.  
  861.         # add pixel strips and update strip offset info
  862.         old_offsets=self.tags['Thumbnail StripOffsets'].values
  863.         old_counts=self.tags['Thumbnail StripByteCounts'].values
  864.         for i in range(len(old_offsets)):
  865.             # update offset pointer (more nasty "strings are immutable" crap)
  866.             offset=self.n2s(len(tiff), strip_len)
  867.             tiff=tiff[:strip_off]+offset+tiff[strip_off+strip_len:]
  868.             strip_off+=strip_len
  869.             # add pixel strip to end
  870.             self.file.seek(self.offset+old_offsets[i])
  871.             tiff+=self.file.read(old_counts[i])
  872.  
  873.         self.tags['TIFFThumbnail']=tiff
  874.  
  875.     # decode all the camera-specific MakerNote formats
  876.     def decode_maker_note(self):
  877.         note=self.tags['EXIF MakerNote']
  878.         make=self.tags['Image Make'].printable
  879.         model=self.tags['Image Model'].printable
  880.  
  881.         # Nikon
  882.         if make == 'NIKON':
  883.             if note.values[0:5] == [78, 105, 107, 111, 110]: # "Nikon"
  884.                 # older model
  885.                 self.dump_IFD(note.field_offset+8, 'MakerNote',
  886.                               dict=MAKERNOTE_NIKON_OLDER_TAGS)
  887.             else:
  888.                 # newer model (E99x or D1)
  889.                 self.dump_IFD(note.field_offset, 'MakerNote',
  890.                               dict=MAKERNOTE_NIKON_NEWER_TAGS)
  891.             return
  892.  
  893.         # Olympus
  894.         if make[:7] == 'OLYMPUS':
  895.             self.dump_IFD(note.field_offset+8, 'MakerNote',
  896.                           dict=MAKERNOTE_OLYMPUS_TAGS)
  897.             return
  898.  
  899.         # Casio
  900.         if make == 'Casio':
  901.             self.dump_IFD(note.field_offset, 'MakerNote',
  902.                           dict=MAKERNOTE_CASIO_TAGS)
  903.             return
  904.  
  905.         # Fujifilm
  906.         if make == 'FUJIFILM':
  907.             # bug: everything else is "Motorola" endian, but the MakerNote
  908.             # is "Intel" endian 
  909.             endian=self.endian
  910.             self.endian='I'
  911.             # bug: IFD offsets are from beginning of MakerNote, not
  912.             # beginning of file header
  913.             offset=self.offset
  914.             self.offset+=note.field_offset
  915.             # process note with bogus values (note is actually at offset 12)
  916.             self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
  917.             # reset to correct values
  918.             self.endian=endian
  919.             self.offset=offset
  920.             return
  921.  
  922.         # Canon
  923.         if make == 'Canon':
  924.             self.dump_IFD(note.field_offset, 'MakerNote',
  925.                           dict=MAKERNOTE_CANON_TAGS)
  926.             for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001),
  927.                       ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)):
  928.                 self.canon_decode_tag(self.tags[i[0]].values, i[1])
  929.             return
  930.  
  931.     # decode Canon MakerNote tag based on offset within tag
  932.     # see http://www.burren.cx/david/canon.html by David Burren
  933.     def canon_decode_tag(self, value, dict):
  934.         for i in range(1, len(value)):
  935.             x=dict.get(i, ('Unknown', ))
  936.             if self.debug:
  937.                 print i, x
  938.             name=x[0]
  939.             if len(x) > 1:
  940.                 val=x[1].get(value[i], 'Unknown')
  941.             else:
  942.                 val=value[i]
  943.             # it's not a real IFD Tag but we fake one to make everybody
  944.             # happy. this will have a "proprietary" type
  945.             self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
  946.                                                  None, None)
  947.  
  948. # process an image file (expects an open file object)
  949. # this is the function that has to deal with all the arbitrary nasty bits
  950. # of the EXIF standard
  951. def process_file(file, debug=0):
  952.     # determine whether it's a JPEG or TIFF
  953.     data=file.read(12)
  954.     if data[0:4] in ['II*\x00', 'MM\x00*']:
  955.         # it's a TIFF file
  956.         file.seek(0)
  957.         endian=file.read(1)
  958.         file.read(1)
  959.         offset=0
  960.     elif data[0:2] == '\xFF\xD8':
  961.         # it's a JPEG file
  962.         # skip JFIF style header(s)
  963.         while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM'):
  964.             length=ord(data[4])*256+ord(data[5])
  965.             file.read(length-8)
  966.             # fake an EXIF beginning of file
  967.             data='\xFF\x00'+file.read(10)
  968.         if data[2] == '\xFF' and data[6:10] == 'Exif':
  969.             # detected EXIF header
  970.             offset=file.tell()
  971.             endian=file.read(1)
  972.         else:
  973.             # no EXIF information
  974.             return {}
  975.     else:
  976.         # file format not recognized
  977.         return {}
  978.  
  979.     # deal with the EXIF info we found
  980.     if debug:
  981.         print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
  982.     hdr=EXIF_header(file, endian, offset, debug)
  983.     ifd_list=hdr.list_IFDs()
  984.     ctr=0
  985.     for i in ifd_list:
  986.         if ctr == 0:
  987.             IFD_name='Image'
  988.         elif ctr == 1:
  989.             IFD_name='Thumbnail'
  990.             thumb_ifd=i
  991.         else:
  992.             IFD_name='IFD %d' % ctr
  993.         if debug:
  994.             print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
  995.         hdr.dump_IFD(i, IFD_name)
  996.         # EXIF IFD
  997.         exif_off=hdr.tags.get(IFD_name+' ExifOffset')
  998.         if exif_off:
  999.             if debug:
  1000.                 print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
  1001.             hdr.dump_IFD(exif_off.values[0], 'EXIF')
  1002.             # Interoperability IFD contained in EXIF IFD
  1003.             intr_off=hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
  1004.             if intr_off:
  1005.                 if debug:
  1006.                     print ' EXIF Interoperability SubSubIFD at offset %d:' \
  1007.                           % intr_off.values[0]
  1008.                 hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
  1009.                              dict=INTR_TAGS)
  1010.         # GPS IFD
  1011.         gps_off=hdr.tags.get(IFD_name+' GPSInfo')
  1012.         if gps_off:
  1013.             if debug:
  1014.                 print ' GPS SubIFD at offset %d:' % gps_off.values[0]
  1015.             hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS)
  1016.         ctr+=1
  1017.  
  1018.     # extract uncompressed TIFF thumbnail
  1019.     thumb=hdr.tags.get('Thumbnail Compression')
  1020.     if thumb and thumb.printable == 'Uncompressed TIFF':
  1021.         hdr.extract_TIFF_thumbnail(thumb_ifd)
  1022.  
  1023.     # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
  1024.     thumb_off=hdr.tags.get('Thumbnail JPEGInterchangeFormat')
  1025.     if thumb_off:
  1026.         file.seek(offset+thumb_off.values[0])
  1027.         size=hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
  1028.         hdr.tags['JPEGThumbnail']=file.read(size)
  1029.  
  1030.     # deal with MakerNote contained in EXIF IFD
  1031.     if hdr.tags.has_key('EXIF MakerNote'):
  1032.         hdr.decode_maker_note()
  1033.  
  1034.     # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
  1035.     # since it's not allowed in a uncompressed TIFF IFD
  1036.     if not hdr.tags.has_key('JPEGThumbnail'):
  1037.         thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
  1038.         if thumb_off:
  1039.             file.seek(offset+thumb_off.values[0])
  1040.             hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
  1041.  
  1042.     return hdr.tags
  1043.  
  1044. # library test/debug function (dump given files)
  1045. if __name__ == '__main__':
  1046.     import sys
  1047.  
  1048.     if len(sys.argv) < 2:
  1049.         print 'Usage: %s files...\n' % sys.argv[0]
  1050.         sys.exit(0)
  1051.  
  1052.     for filename in sys.argv[1:]:
  1053.         try:
  1054.             file=open(filename, 'rb')
  1055.         except:
  1056.             print filename, 'unreadable'
  1057.             print
  1058.             continue
  1059.         print filename+':'
  1060.         # data=process_file(file, 1) # with debug info
  1061.         data=process_file(file)
  1062.         if not data:
  1063.             print 'No EXIF information found'
  1064.             continue
  1065.  
  1066.         x=data.keys()
  1067.         x.sort()
  1068.         for i in x:
  1069.             if i in ('JPEGThumbnail', 'TIFFThumbnail'):
  1070.                 continue
  1071.             try:
  1072.                 print '   %s (%s): %s' % \
  1073.                       (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
  1074.             except:
  1075.                 print 'error', i, '"', data[i], '"'
  1076.         if data.has_key('JPEGThumbnail'):
  1077.             print 'File has JPEG thumbnail'
  1078.         print
  1079.